package tagdb.ui;

import com.compomics.util.examples.BareBonesBrowserLaunch;
import com.compomics.util.experiment.biology.Ion.IonType;
import com.compomics.util.experiment.biology.IonFactory;
import com.compomics.util.experiment.biology.NeutralLoss;
import com.compomics.util.experiment.biology.PTMFactory;
import com.compomics.util.experiment.biology.Protein;
import com.compomics.util.experiment.biology.ions.PeptideFragmentIon;
import com.compomics.util.experiment.identification.SequenceFactory;
import com.compomics.util.experiment.massspectrometry.SpectrumFactory;
import com.compomics.util.preferences.AnnotationPreferences;
import com.compomics.util.protein.Header;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import net.jimmc.jshortcut.JShellLink;
import org.jdesktop.swingx.JXList;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.JXTitledPanel;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import tagdb.algorithm.factory.MutationFactory;
import tagdb.algorithm.match.PeptideMatcher;
import tagdb.algorithm.match.PeptideSpectrumMatch;
import tagdb.algorithm.tag.Tag;
import tagdb.algorithm.tag.TagGenerator;
import tagdb.algorithm.tag.TagSearch;
import tagdb.denovo.DenovoEntry;
import tagdb.denovo.DenovoHit;
import tagdb.denovo.DenovoParser;
import tagdb.denovo.DenovoResult;
import tagdb.fasta.Database;
import tagdb.io.*;
import tagdb.job.PepnovoJob;
import tagdb.model.*;
import tagdb.model.params.DenovoParams;
import tagdb.model.params.SearchParams;
import tagdb.ui.filter.DenovoSearchFilter;
import tagdb.ui.util.*;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.filechooser.FileFilter;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.*;
import java.util.List;

/**
 * <b>MainFrame</b>
 * <p>
 * This class manages the graphical user interface of the application.
 * </p>
 *
 * @author T.Muth
 */
public class MainFrame extends JFrame {

    /**
     * Turns of the gradient painting for the bar charts.
     */
    static {
        XYBarRenderer.setDefaultBarPainter(new StandardXYBarPainter());
    }

    /**
     * Modification file.
     */
    private final static String MODIFICATION_FILE = "resources/conf/tagdb_mods.xml";

    /**
     * User modification file.
     */
    private final static String USER_MODIFICATION_FILE = "resources/conf/tagdb_usermods.xml";

    /**
     * If set to true all messages will be sent to a log file.
     */
    private static boolean useLogFile = true;

    private MainFrame frame;
    private JPanel searchPnl;
    private JPanel denovoParamsPnl;
    private ResultPanel resultPnl;
    private TagSearchResultPanel tagSearchResultPnl;
    private JMenuBar menuBar;
    private CellConstraints cc;
    private JMenuItem exitItem;
    private JLabel spectraFilesLbl;
    private JButton addSpectraBtn;
    private JButton clearSpectraBtn;
    private JTextField spectraFilesTtf;
    private JPanel inputPnl;
    private JPanel exeFolderPnl;
    private JButton browsePepnovoBtn;
    private JLabel pepnovoLbl;
    private JTextField pepnovoTtf;
    private JPanel outputPnl;
    private JLabel outputLbl;
    private JTextField outputTtf;
    private File outputFolder;
    private JButton browseOutputBtn;
    private JPanel startPnl;
    private JLabel progressLbl;
    private JProgressBar searchPrg;
    private JButton runPepNovoBtn;
    private SearchTask searchTask;
    private ParseTask parseTask;
    private LogPanel logPnl;

    /**
     * The annotation preferences.
     */
    private AnnotationPreferences annotationPreferences = new AnnotationPreferences();

    // List of of the spectrum files.
    private List<File> spectraFiles = new ArrayList<File>();

    // Last selected file path.
    private String lastSelected = getJarFilePath();

    // List of the output filenames.
    private List<String> outputFiles;

    // Map containing the pepnovo hit results.
    private Map<String, List<DenovoHit>> resultsMap = new HashMap<String, List<DenovoHit>>();

    // Map containing the generated tags.
    private Map<String, List<Tag>> tagMap = new HashMap<String, List<Tag>>();

    // Map of the query spectra in MascotGenericFile format.
    public Map<String, MascotGenericFile> querySpectra = new HashMap<String, MascotGenericFile>();

    /**
     * The charge menus.
     */
    private HashMap<Integer, JCheckBoxMenuItem> chargeMenus = new HashMap<Integer, JCheckBoxMenuItem>();

    /**
     * The neutral loss menus.
     */
    private HashMap<NeutralLoss, JCheckBoxMenuItem> lossMenus = new HashMap<NeutralLoss, JCheckBoxMenuItem>();

    private JComboBox enzymesCbx;
    private JComboBox modelCbx;
    private JSpinner fragTolSpn;
    private JSpinner precTolSpn;
    private JSpinner numSolutionsSpn;
    private JPanel settingsPnl;
    private JPanel dbSearchPnl;
    private JPanel fastaFilePnl;
    private JLabel fastaDbLbl;
    private JTextField fastaDbTtf;
    private JButton browseFastaDbBtn;
    private JButton runTagSearchBtn;
    public String queryFilePath;
    private JScrollPane modsScp;
    private JScrollPane mutationsScp;
    private JPanel selectModsPnl;
    private JCheckBox spectrumChargeCbx;
    private JCheckBox spectrumMzCbx;
    private JCheckBox lowQualFilterCbx;
    private JPanel startSearchPnl;
    public TagSearchResult tagSearchResult;
    private LogPanel logPnl2;
    private JMenuItem openItem;
    private JPanel mainPnl;
    private JPanel dbSearchSettingsPnl;
    private JPanel dbSearchMainPnl;
    private JProgressBar searchPrg2;
    private JSpinner fragTolSpn2;
    private JSpinner precTolSpn2;
    private JSpinner missedCleavagesSpn;
    private JTabbedPane tabPane;
    private JXList modList;
    private StatsPanel statsPnl;
    private String filename;
    private JMenuItem exportProteinsItem;
    private String lastSelectedFolder = System.getProperty("user.home");
    private JMenuItem exportPeptidesItem;
    private JMenuItem exportPSMsItem;
    private JMenuItem exportScoresItem;
    private JMenuItem aboutFrameItem;
    private JMenuItem helpFrameItem;
    private JMenuBar annotationMenuBar;
    private JMenu ionsMenu;
    private JCheckBoxMenuItem aIonCheckBoxMenuItem;
    private JCheckBoxMenuItem bIonCheckBoxMenuItem;
    private JCheckBoxMenuItem cIonCheckBoxMenuItem;
    private JCheckBoxMenuItem xIonCheckBoxMenuItem;
    private JCheckBoxMenuItem yIonCheckBoxMenuItem;
    private JCheckBoxMenuItem zIonCheckBoxMenuItem;
    private JMenu splitterMenu1;
    private JMenu splitterMenu2;
    private JMenu splitterMenu3;
    private JMenu splitterMenu4;
    private JMenu splitterMenu5;
    private JMenu splitterMenu6;
    private JMenu splitterMenu7;
    private JMenu otherMenu;
    private JMenu lossMenu;
    private JMenu settingsMenu;
    private JMenu chargeMenu;
    private JMenu deNovoMenu;
    private JCheckBoxMenuItem reporterIonsCheckMenuItem;
    private JCheckBoxMenuItem precursorCheckMenuItem;
    private JCheckBoxMenuItem immoniumIonsCheckMenuItem;
    private JCheckBoxMenuItem forwardIonsDeNovoCheckBoxMenuItem;
    private JCheckBoxMenuItem rewindIonsDeNovoCheckBoxMenuItem;
    private JCheckBoxMenuItem allCheckBoxMenuItem;
    private JCheckBoxMenuItem barsCheckBoxMenuItem;
    private ButtonGroup deNovoChargeButtonGroup;
    private JCheckBoxMenuItem deNovoChargeOneJRadioButtonMenuItem;
    private JCheckBoxMenuItem deNovoChargeTwoJRadioButtonMenuItem;

    private DenovoParams params;
    private JButton cancelBtn;
    private PepnovoJob job;

    /**
     * The factory used to read the modifications.
     */
    private PTMFactory ptmFactory = PTMFactory.getInstance();

    /**
     * The possible modifications as list.
     */
    private List<String> mods;

    /**
     * The possible mutations as list.
     */
    private List<String> mutations;

    /**
     * The table for the modifications to choose.
     */
    private JXTable modsTable;

    /**
     * The selected modifications.
     */
    private Set<String> selectedMods;

    /**
     * The selected mutations.
     */
    private Set<String> selectedMutations;

    /**
     * The selected mutations panel.
     */
    private JPanel selectMutationsPnl;

    /**
     * The table of mutations.
     */
    private JXTable mutationsTable;

    /**
     * Button to show a dialog for configuration of the modifications.
     */
    private JButton configModsBtn;
    private JButton configMutationsBtn;

    /**
     * SpectrumFactory instance.
     */
    private SpectrumFactory spectrumFactory;
    private DenovoSearchResult denovoSearchResult;

    /**
     * Constructor for the DeNovoSearcherUI
     */
    public MainFrame() {
        // Application title
        super(Constants.APPTITLE + " " + new tagdb.util.Properties().getVersion());
        
        // check for new version on google code
        checkForNewVersion(new tagdb.util.Properties().getVersion());
        
        // set up log file
        setUpLogFile();
        
        // add desktop shortcut
        addDesktopShortCut();

        // Load modifications
        try {
            ptmFactory.importModifications(getModificationsFile(), false);
        } catch (Exception e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, "Error while reading " + MODIFICATION_FILE + ".", "Modification File Error", JOptionPane.ERROR_MESSAGE);
        }
        try {
            ptmFactory.importModifications(getUserModificationsFile(), true);
        } catch (Exception e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, "Error while reading " + USER_MODIFICATION_FILE + ".", "Modification File Error", JOptionPane.ERROR_MESSAGE);
        }

        // Init the selected
        selectedMods = new HashSet<String>();
        selectedMutations = new HashSet<String>();

        // @TODO: add more neutral losses?
        IonFactory.getInstance().addDefaultNeutralLoss(NeutralLoss.NH3);
        IonFactory.getInstance().addDefaultNeutralLoss(NeutralLoss.H2O);
        
        // Frame size
        this.setMinimumSize(new Dimension(Constants.MAINFRAME_WIDTH, Constants.MAINFRAME_HEIGHT));
        this.setPreferredSize(new Dimension(Constants.MAINFRAME_WIDTH, Constants.MAINFRAME_HEIGHT));
        frame = this;

        // Init components
        initComponents();

        // set the default PepNovo folder
        if (new File(getJarFilePath() + "/resources/conf/PepNovo").exists()) {
            pepnovoTtf.setText("" + new File(getJarFilePath() + "/resources/conf/PepNovo"));
        }

        // Sets the icon of the frame
        setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/icons/tag-db.png")));

        // Get the content pane
        Container cp = this.getContentPane();
        cp.setLayout(new BorderLayout());
        cp.add(menuBar, BorderLayout.NORTH);

        // Icons
        ImageIcon searchIcon = new ImageIcon(getClass().getResource("/icons/zoom2.png"));
        //ImageIcon paramIcon = new ImageIcon(getClass().getResource("/resources/dns/resources/icons/pref.png"));
        ImageIcon resultIcon = new ImageIcon(getClass().getResource("/icons/searchresult2.png"));
        ImageIcon dbsearchIcon = new ImageIcon(getClass().getResource("/icons/dbsearch2.png"));
        ImageIcon dbSearchResultIcon = new ImageIcon(getClass().getResource("/icons/table2.png"));
        ImageIcon statsIcon = new ImageIcon(getClass().getResource("/icons/statistics.png"));

        tabPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.WRAP_TAB_LAYOUT);
        tabPane.addTab("De Novo Search", searchIcon, mainPnl);
        tabPane.addTab("De Novo Results", resultIcon, resultPnl);
        tabPane.addTab("Tag-DB Search", dbsearchIcon, dbSearchMainPnl);
        tabPane.addTab("Tag-DB Search Results", dbSearchResultIcon, tagSearchResultPnl);
        tabPane.addTab("Statistics", statsIcon, statsPnl);

        // Disable the other tabs by default
        tabPane.setEnabledAt(1, false);
        tabPane.setEnabledAt(2, false);
        tabPane.setEnabledAt(3, false);
        tabPane.setEnabledAt(4, false);
        
        // move the annotation menu bar to the currently opened results tab
        tabPane.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                moveAnnotationMenuBar();
            }
        });

        // Add tab pane to content pane
        cp.add(tabPane);

        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setResizable(true);
        this.pack();

        // Center in the screen
        setLocationRelativeTo(this);
        this.setVisible(true);
    }
    
    /**
     * Move the annotation menu bar to the currently opened results tab.
     */
    private void moveAnnotationMenuBar() {
        if (tabPane.getSelectedIndex() == 2) { // de novo results tab
            resultPnl.displayAnnotationMenu();
        } else if (tabPane.getSelectedIndex() == 4) { // tag search results tab
            tagSearchResultPnl.displayAnnotationMenu();
        }
    }

    /**
     * Check if the user wants to add a desktop shortcut. Only asks the first 
     * time after installing a new version.
     */
    private void addDesktopShortCut() {
        
        if (!getJarFilePath().equalsIgnoreCase(".")
                && System.getProperty("os.name").lastIndexOf("Windows") != -1
                && new File(getJarFilePath() + "/resources/conf/firstRun").exists()) {

            // @TODO: add support for desktop icons in mac and linux??

            // delete the firstRun file such that the user is not asked the next time around
            new File(getJarFilePath() + "/resources/conf/firstRun").delete();

            int value = JOptionPane.showConfirmDialog(null,
                    "Create a shortcut to " + Constants.APPTITLE + " on the desktop?",
                    "Create Desktop Shortcut?",
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE);

            if (value == JOptionPane.YES_OPTION) {
                addShortcutAtDeskTop();
            }
        }
    }
    
    /**
     * Check if a newer version of Tag-DB is available.
     *
     * @param currentVersion the version number of the currently running version
     */
    private static void checkForNewVersion(String currentVersion) {

        try {
            boolean deprecatedOrDeleted = false;

            URL downloadPage = new URL(
                    "http://code.google.com/p/tag-db/downloads/detail?name=tag-db-" + currentVersion + ".zip");

            if ((java.net.HttpURLConnection) downloadPage.openConnection() != null) {

                int responseCode = ((java.net.HttpURLConnection) downloadPage.openConnection()).getResponseCode();

                // 404 means that the file no longer exists, which means that
                // the running version is no longer available for download,
                // which again means that a never version is available.
                if (responseCode == 404) {
                    deprecatedOrDeleted = true;
                } else {

                    // also need to check if the available running version has been
                    // deprecated (but not deleted)
                    BufferedReader in = new BufferedReader(
                            new InputStreamReader(downloadPage.openStream()));

                    String inputLine;

                    while ((inputLine = in.readLine()) != null && !deprecatedOrDeleted) {
                        if (inputLine.lastIndexOf("Deprecated") != -1
                                && inputLine.lastIndexOf("Deprecated Downloads") == -1
                                && inputLine.lastIndexOf("Deprecated downloads") == -1) {
                            deprecatedOrDeleted = true;
                        }
                    }

                    in.close();
                }

                // informs the user about an updated version, unless the user
                // is running a beta version
                if (deprecatedOrDeleted && currentVersion.lastIndexOf("beta") == -1
                        && currentVersion.lastIndexOf("${version}") == -1) {
                    int option = JOptionPane.showConfirmDialog(null,
                            "A newer version of Tag-DB is available.\n"
                            + "Do you want to upgrade?",
                            "Upgrade Available",
                            JOptionPane.YES_NO_CANCEL_OPTION);
                    if (option == JOptionPane.YES_OPTION) {
                        BareBonesBrowserLaunch.openURL("http://tag-db.googlecode.com/");
                        System.exit(0);
                    } else if (option == JOptionPane.CANCEL_OPTION) {
                        System.exit(0);
                    }
                }
            }
        } catch (UnknownHostException e) {
            // ignore exception
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Returns the modifications file.
     *
     * @return the modifications file
     */
    public File getModificationsFile() {
        File result = new File(getJarFilePath() + File.separator + MODIFICATION_FILE);

        if (!result.exists()) {
            JOptionPane.showMessageDialog(null, MODIFICATION_FILE + " not found.", "Modification File Error", JOptionPane.ERROR_MESSAGE);
        }
        return result;
    }

    /**
     * Returns the user defined modifications file.
     *
     * @return the user defined modifications file
     */
    public File getUserModificationsFile() {
        File result = new File(getJarFilePath() + File.separator + USER_MODIFICATION_FILE);
        if (!result.exists()) {
            JOptionPane.showMessageDialog(null, USER_MODIFICATION_FILE + " not found.", "Modification File Error", JOptionPane.ERROR_MESSAGE);
        }
        return result;
    }

    /**
     * Set up the log file.
     */
    private void setUpLogFile() {
        if (useLogFile && !getJarFilePath().equalsIgnoreCase(".")) {
            try {
                String path = getJarFilePath() + "/resources/conf/Tag-DB.log";

                File file = new File(path);
                System.setOut(new java.io.PrintStream(new FileOutputStream(file, true)));
                System.setErr(new java.io.PrintStream(new FileOutputStream(file, true)));

                // creates a new log file if it does not exist
                if (!file.exists()) {
                    file.createNewFile();

                    FileWriter w = new FileWriter(file);
                    BufferedWriter bw = new BufferedWriter(w);

                    bw.close();
                    w.close();
                }
            } catch (Exception e) {
                JOptionPane.showMessageDialog(
                        null, "An error occured when trying to create the Tag-DB log file.",
                        "Error Creating Log File", JOptionPane.ERROR_MESSAGE);
                e.printStackTrace();
            }
        }
    }
    
    /**
     * Main method ==> Entry point to the application.
     *
     * @param args
     */
    public static void main(String[] args) {
        // Set the look&feel
        GuiUtil.setLookAndFeel();

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new MainFrame();
            }
        });
    }

    /**
     * Initialize the components.
     */
    private void initComponents() {

        // Cell constraints
        cc = new CellConstraints();

        // Menu
        constructMenu();

        // Parameters panel
        constructDenovoParamsPanel();

        // Search panel
        constructSearchPanel();

        // Setup annotation menu bar
        setupAnnotationMenuBar();

        // Result panel
        resultPnl = new ResultPanel(this);

        // Tag search panel
        constructTagSearchPanel();

        statsPnl = new StatsPanel();

        // Tag Search Result panel
        tagSearchResultPnl = new TagSearchResultPanel(this);

        // Update the annotations preferences once
        annotationPreferences.useAutomaticAnnotation(true);
        updateAnnotationPreferences();
    }

    /**
     * Constructs tag search panel.
     */
    private void constructTagSearchPanel() {

        dbSearchPnl = new JPanel();
        dbSearchPnl.setLayout(new FormLayout("5dlu, p, 5dlu, p, 5dlu", "5dlu, t:p, 10dlu, t:p, 10dlu, t:p, 10dlu"));

        // Fasta file location panel
        fastaFilePnl = new JPanel(new FormLayout("5dlu, 60dlu, 5dlu, l:p, 10dlu, p, 5dlu, 20dlu, 5dlu", "10dlu, t:p, 10dlu"));
        JXTitledPanel fastaFileTtlPnl = PanelFactory.createTitledPanel("Fasta Database", fastaFilePnl);

        // Fasta database location label
        fastaDbLbl = new JLabel("Fasta File:");

        // Fasta database location.
        fastaDbTtf = new JTextField(30);
        fastaDbTtf.setEditable(false);
        fastaDbTtf.setMaximumSize(new Dimension(fastaDbTtf.getMaximumSize().width, fastaDbTtf.getPreferredSize().height));

        // Select button for browsing for the Fasta database folder.
        browseFastaDbBtn = new JButton(openFastaFileAction());
        browseFastaDbBtn.setText("Browse");
        fastaFilePnl.add(fastaDbLbl, cc.xy(2, 2));
        fastaFilePnl.add(fastaDbTtf, cc.xy(4, 2));
        fastaFilePnl.add(browseFastaDbBtn, cc.xy(6, 2));
        dbSearchPnl.add(fastaFileTtlPnl, cc.xy(2, 2));

        dbSearchSettingsPnl = new JPanel();
        dbSearchSettingsPnl.setLayout(new FormLayout("5dlu, p:g, 5dlu, p, 2dlu, p, 5dlu", "5dlu, p, 5dlu, p, 5dlu, p, 5dlu"));
        JXTitledPanel dbSearchSettingsTtlPnl = PanelFactory.createTitledPanel("Tag-DB Search Settings", dbSearchSettingsPnl);

        JPanel dbSearchRightPnl = new JPanel();
        dbSearchRightPnl.setLayout(new FormLayout("f:p, 5dlu", "5dlu, p, 10dlu, p, 5dlu, p, 5dlu"));

        // Fragment mass tolerance
        dbSearchSettingsPnl.add(new JLabel("Fragment Mass Tolerance:"), cc.xy(2, 2));
        fragTolSpn2 = new JSpinner(new SpinnerNumberModel(0.5, 0.0, null, 0.1));
        fragTolSpn2.setEditor(new JSpinner.NumberEditor(fragTolSpn2, "0.0"));
        fragTolSpn2.setToolTipText("Choose the fragment mass tolerance.");
        dbSearchSettingsPnl.add(fragTolSpn2, cc.xy(4, 2));
        dbSearchSettingsPnl.add(new JLabel("Da"), cc.xy(6, 2));

        // Precursor mass tolerance
        dbSearchSettingsPnl.add(new JLabel("Precursor Mass Tolerance:"), cc.xy(2, 4));
        precTolSpn2 = new JSpinner(new SpinnerNumberModel(1.0, 0.0, null, 0.10));
        precTolSpn2.setEditor(new JSpinner.NumberEditor(precTolSpn2, "0.00"));
        precTolSpn2.setToolTipText("Choose the precursor mass tolerance");
        dbSearchSettingsPnl.add(precTolSpn2, cc.xy(4, 4));
        dbSearchSettingsPnl.add(new JLabel("Da"), cc.xy(6, 4));

        // Maximum number of missed cleavages
        dbSearchSettingsPnl.add(new JLabel("No. Missed Cleavages (max):"), cc.xy(2, 6));
        missedCleavagesSpn = new JSpinner(new SpinnerNumberModel(1, 0, 2, 1));
        missedCleavagesSpn.setToolTipText("Select the maximum number of missed cleavages..");
        dbSearchSettingsPnl.add(missedCleavagesSpn, cc.xy(4, 6));

        dbSearchPnl.add(dbSearchSettingsTtlPnl, cc.xywh(4, 2, 1, 3));

        // Add the output panel to the search panel
        startSearchPnl = new JPanel(new FormLayout("5dlu, l:p, 5dlu, l:p, 10dlu, p, 5dlu", "10dlu, p, 10dlu"));
        JXTitledPanel startSearchTtlPnl = PanelFactory.createTitledPanel("Tag-DB Search Run", startSearchPnl);

        // Progress label
        JLabel progressLbl2 = new JLabel("Progress:");

        // Progress bar
        searchPrg2 = new JProgressBar();
        searchPrg2.setStringPainted(true);

        // Start tag search button
        runTagSearchBtn = new JButton("Start");
        runTagSearchBtn.setToolTipText("Start the tadg-based database search run");
        runTagSearchBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                runSearchBtnPressed();
            }
        });

        startSearchPnl.add(progressLbl2, cc.xy(2, 2));
        startSearchPnl.add(searchPrg2, cc.xy(4, 2));
        startSearchPnl.add(runTagSearchBtn, cc.xy(6, 2));
        startSearchPnl.add(runTagSearchBtn, cc.xy(6, 2));

        logPnl2 = new LogPanel(false);
        JXTitledPanel logTtlPnl2 = PanelFactory.createTitledPanel("Logging", logPnl2);

        logPnl2.setPreferredSize(new Dimension(Constants.LOGPANEL_WIDTH, Constants.LOGPANEL_HEIGHT));

        dbSearchMainPnl = new JPanel(new FormLayout("p, 5dlu, p", "t:p"));
        dbSearchPnl.add(logTtlPnl2, cc.xy(2, 4));

        dbSearchRightPnl.add(dbSearchSettingsTtlPnl, cc.xy(1, 2));

        // Mutations Panel
        selectMutationsPnl = new JPanel();
        selectMutationsPnl.setLayout(new FormLayout("5dlu, p:g, 5dlu", "5dlu, f:p:g, 5dlu"));
        JXTitledPanel mutationsTtlPnl = PanelFactory.createTitledPanel("Mutations", selectMutationsPnl);

        configMutationsBtn = new JButton("Configure");
        mutationsTtlPnl.setRightDecoration(configMutationsBtn);

        configMutationsBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new MutationsDialog(frame);
            }
        });

        mutationsTable = new JXTable(new MutationsTableModel()) {
            public Border padding = BorderFactory.createEmptyBorder(0, 2, 0, 2);
            @Override
            public Component prepareRenderer(TableCellRenderer renderer,
                                             int row, int column) {
                Component comp = super.prepareRenderer(renderer, row, column);
                if (comp instanceof JComponent) {
                    ((JComponent) comp).setBorder(padding);
                }
                return comp;
            }
            @Override
            public void setValueAt(Object aValue, int row, int column) {
                super.setValueAt(aValue, row, column);
                if (column == 0) {
                    if((Boolean) aValue == true) {
                        selectedMutations.add(getValueAt(row, 1).toString());
                    } else {
                        selectedMutations.remove(getValueAt(row, 1).toString());
                    }
                }
            }
        };

        // Fill the mutations table
        updateMutations();

        // Update the modifications: updateModifications();
        mutationsScp = new JScrollPane(mutationsTable);
        mutationsScp.setPreferredSize(new Dimension(250, 150));
        mutationsScp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        mutationsScp.setToolTipText("Choose possible PTMs");
        mutationsScp.setEnabled(false);
        selectMutationsPnl.add(mutationsScp, cc.xy(2, 2));
        setupMutationsTable();
        dbSearchRightPnl.add(mutationsTtlPnl, cc.xy(1, 4));
        dbSearchRightPnl.add(startSearchTtlPnl, cc.xy(1, 6));

        dbSearchMainPnl.add(dbSearchPnl, cc.xy(1, 1));
        dbSearchMainPnl.add(dbSearchRightPnl, cc.xy(3, 1));
    }

    /**
     * Constructs the denovo parameter panel
     */
    private void constructDenovoParamsPanel() {

        denovoParamsPnl = new JPanel();
        denovoParamsPnl.setLayout(new FormLayout("5dlu, f:p, 5dlu", "5dlu, f:p, 10dlu, p, 10dlu, p, 5dlu"));

        settingsPnl = new JPanel();
        JXTitledPanel settingsTtlPnl = PanelFactory.createTitledPanel("Search Settings", settingsPnl);
        settingsPnl.setLayout(new FormLayout("5dlu, p, 5dlu, p:g, 5dlu, p, 2dlu, p, 5dlu", "5dlu, p, 5dlu, p, 5dlu, p, 5dlu, p, 5dlu, p, 5dlu, p, 3dlu, p, 3dlu, p, 4dlu"));

        // Enzymes
        settingsPnl.add(new JLabel("Enzyme: "), cc.xy(2, 2));
        enzymesCbx = new JComboBox(Constants.ENZYMES);
        enzymesCbx.setToolTipText("Choose the enzyme of the protein digest.");
        settingsPnl.add(enzymesCbx, cc.xyw(4, 2, 5));

        // Model
        settingsPnl.add(new JLabel("Model: "), cc.xy(2, 4));
        modelCbx = new JComboBox(Constants.MODELS);
        modelCbx.setToolTipText("Select the fragmentation model.");
        settingsPnl.add(modelCbx, cc.xyw(4, 4, 5));

        // Fragment mass tolerance
        settingsPnl.add(new JLabel("Fragment Mass Tolerance:"), cc.xyw(2, 6, 3));
        fragTolSpn = new JSpinner(new SpinnerNumberModel(0.5, 0.0, null, 0.1));
        fragTolSpn.setEditor(new JSpinner.NumberEditor(fragTolSpn, "0.0"));
        fragTolSpn.setToolTipText("Choose the fragment mass tolerance.");
        settingsPnl.add(fragTolSpn, cc.xy(6, 6));
        settingsPnl.add(new JLabel("Da"), cc.xy(8, 6));

        // Precursor mass tolerance
        settingsPnl.add(new JLabel("Precursor Mass Tolerance:"), cc.xyw(2, 8, 3));
        precTolSpn = new JSpinner(new SpinnerNumberModel(1.0, 0.0, null, 0.1));
        precTolSpn.setEditor(new JSpinner.NumberEditor(precTolSpn, "0.00"));
        precTolSpn.setToolTipText("Choose the precursor mass tolerance");
        settingsPnl.add(precTolSpn, cc.xy(6, 8));
        settingsPnl.add(new JLabel("Da"), cc.xy(8, 8));

        // Maximum number of peptides
        settingsPnl.add(new JLabel("No. Solutions (max. 20):"), cc.xyw(2, 10, 3));
        numSolutionsSpn = new JSpinner(new SpinnerNumberModel(10, 0, 20, 1));
        numSolutionsSpn.setToolTipText("Select the maximum number of solutions for de-novo sequencing.");
        settingsPnl.add(numSolutionsSpn, cc.xy(6, 10));

        // Use spectrum charge selection
        spectrumChargeCbx = new JCheckBox("Use Spectrum Charge (No Correction)");
        settingsPnl.add(spectrumChargeCbx, cc.xyw(2, 12, 5));

        // Use spectrum precursor m/z selection
        spectrumMzCbx = new JCheckBox("Use Spectrum Precursor m/z (No Correction)");
        settingsPnl.add(spectrumMzCbx, cc.xyw(2, 14, 5));

        // Low quality filter: Turned on by default.
        lowQualFilterCbx = new JCheckBox("Filter Low Quality Spectra");
        lowQualFilterCbx.setSelected(true);
        settingsPnl.add(lowQualFilterCbx, cc.xyw(2, 16, 5));

        // Panel PTMs
        selectModsPnl = new JPanel();
        selectModsPnl.setLayout(new FormLayout("5dlu, p:g, 5dlu", "5dlu, f:p:g, 5dlu"));
        JXTitledPanel modsTtlPnl = PanelFactory.createTitledPanel("Modifications", selectModsPnl);

        configModsBtn = new JButton("Configure");
        configModsBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new ModsDialog(frame);
            }
        });

        modsTtlPnl.setRightDecoration(configModsBtn);

        modsTable = new JXTable(new ModsTableModel()) {
            public Border padding = BorderFactory.createEmptyBorder(0, 2, 0, 2);
            @Override
            public Component prepareRenderer(TableCellRenderer renderer,
                                             int row, int column) {
                Component comp = super.prepareRenderer(renderer, row, column);
                if (comp instanceof JComponent) {
                    ((JComponent) comp).setBorder(padding);
                }
                return comp;
            }
            @Override
            public void setValueAt(Object aValue, int row, int column) {
                super.setValueAt(aValue, row, column);
                if (column == 0) {
                    if((Boolean) aValue == true) {
                        selectedMods.add(getValueAt(row, 1).toString());
                    } else {
                        selectedMods.remove(getValueAt(row, 1).toString());
                    }
                }
            }
        };

        updateModifications();
        modsScp = new JScrollPane(modsTable);
        modsScp.setPreferredSize(new Dimension(250, 150));
        modsScp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        modsScp.setToolTipText("Choose possible PTMs");
        modsScp.setEnabled(false);
        selectModsPnl.add(modsScp, cc.xy(2, 2));
        setupModsTable();

        // Add panels
        denovoParamsPnl.add(settingsTtlPnl, cc.xy(2, 2));
        denovoParamsPnl.add(modsTtlPnl, cc.xy(2, 4));

        startPnl = new JPanel(new FormLayout("5dlu, l:p, 5dlu, l:p, 10dlu, p, 5dlu, p, 5dlu", "10dlu, p, 10dlu"));
        JXTitledPanel startTtlPnl = PanelFactory.createTitledPanel("De Novo Search Run", startPnl);

        // Progress label
        progressLbl = new JLabel("Progress:");

        // Progress bar
        searchPrg = new JProgressBar();
        searchPrg.setStringPainted(true);

        // Run de novo search button
        runPepNovoBtn = new JButton("Start");
        runPepNovoBtn.setEnabled(false);
        runPepNovoBtn.setToolTipText("Start the de novo search");
        runPepNovoBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                searchBtnPressed();
            }
        });

        // Cancel de novo search button
        cancelBtn = new JButton("Cancel");
        cancelBtn.setEnabled(false);
        cancelBtn.setToolTipText("Cancel the de novo search");
        cancelBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                cancelBtnPressed();
            }
        });

        startPnl.add(progressLbl, cc.xy(2, 2));
        startPnl.add(searchPrg, cc.xy(4, 2));
        startPnl.add(runPepNovoBtn, cc.xy(6, 2));
        startPnl.add(cancelBtn, cc.xy(8, 2));

        // Add the start pnale to the right panel
        denovoParamsPnl.add(startTtlPnl, cc.xy(2, 6));
    }

    /**
     * This method updates the modifications.
     */
    public void updateModifications() {
        ((DefaultTableModel) modsTable.getModel()).setRowCount(0);

        mods = new ArrayList<String>();
        mods.addAll(ptmFactory.getDefaultModifications());
        mods.addAll(ptmFactory.getUserModifications());

        // Iterate the mods.
        for(String mod : mods) {
            // De-selected by default
            boolean selected = false;
            // Fixed mod CARBAMIDOMETHYL C -- selected
            if(mod.equals("carbamidomethyl c")){
                selectedMods.add(mod);
                selected = true;
            }
            ((ModsTableModel) modsTable.getModel()).addRow(new Object[]{
                    selected,
                    mod
            });
        }
    }

    /**
     * This method updates the mutations.
     */
    public void updateMutations() {
        ((DefaultTableModel) mutationsTable.getModel()).setRowCount(0);

        mutations = new ArrayList<String>();
        mutations.addAll(MutationFactory.getMutationsAsString());

        // Iterate the mods.
        for(String mut : mutations) {
            // De-selected by default
            boolean selected = false;
            ((MutationsTableModel) mutationsTable.getModel()).addRow(new Object[]{
                    selected,
                    mut
            });
        }
    }

    /**
     * This method sets up the modifications table.
     */
    private void setupModsTable() {

        modsTable.setAutoCreateRowSorter(true);
        modsTable.getTableHeader().setReorderingAllowed(false);

        // make sure that the scroll panes are see-through
        modsScp.getViewport().setOpaque(false);

        // Selection background
        modsTable.setSelectionBackground(TableConfig.ROW_SELECTION_BACKGROUND_COLOR);

        // Add striping effect
        modsTable.addHighlighter(TableConfig.getSimpleStriping());
        modsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        // the index column
        modsTable.getColumn("").setMaxWidth(19);
        modsTable.getColumn("").setMinWidth(19);
    }

    /**
     * This method sets up the mutations table.
     */
    private void setupMutationsTable() {

        mutationsTable.setAutoCreateRowSorter(true);
        mutationsTable.getTableHeader().setReorderingAllowed(false);

        // make sure that the scroll panes are see-through
        mutationsScp.getViewport().setOpaque(false);

        // Selection background
        mutationsTable.setSelectionBackground(TableConfig.ROW_SELECTION_BACKGROUND_COLOR);

        // Add striping effect
        mutationsTable.addHighlighter(TableConfig.getSimpleStriping());
        mutationsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        // the index column
        mutationsTable.getColumn("").setMaxWidth(19);
        mutationsTable.getColumn("").setMinWidth(19);
    }

    /**
     * Table model for the modifications table.
     */
    private class ModsTableModel extends DefaultTableModel {

        @Override
        public int getColumnCount() {
            return 2;
        }

        @Override
        public String getColumnName(int column) {
            switch (column) {
                case 0:
                    return "";
                case 1:
                    return "Name";
                default:
                    return "";
            }
        }

        @Override
        public boolean isCellEditable(int row, int col) {
            if (col == 0) {
                return true;
            }else {
                return false;
            }
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            switch (columnIndex) {
                case 0:
                    return Boolean.class;
                case 1:
                    return String.class;
                default:
                    return String.class;
            }
        }
    }

    /**
     * Table model for the mutations table.
     */
    private class MutationsTableModel extends DefaultTableModel {

        @Override
        public int getColumnCount() {
            return 2;
        }

        @Override
        public String getColumnName(int column) {
            switch (column) {
                case 0:
                    return "";
                case 1:
                    return "Name";
                default:
                    return "";
            }
        }

        @Override
        public boolean isCellEditable(int row, int col) {
            if (col == 0) {
                return true;
            }else {
                return false;
            }
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            switch (columnIndex) {
                case 0:
                    return Boolean.class;
                case 1:
                    return String.class;
                default:
                    return String.class;
            }
        }
    }

    private void constructMenu() {
        menuBar = new JMenuBar();
        // File Menu
        JMenu fileMenu = new JMenu();
        exitItem = new JMenuItem();
        fileMenu.setText("File");

        openItem = new JMenuItem();
        openItem.setText("Import Results");
        openItem.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                openFileTriggered();
            }
        });

        exitItem.setText("Exit");
        exitItem.setIcon(new ImageIcon(getClass().getResource("/icons/exit.gif")));

        exitItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });
        fileMenu.add(openItem);
        fileMenu.add(exitItem);
        menuBar.add(fileMenu);

        // Export menu
        JMenu exportMenu = new JMenu();
        exportMenu.setText("Export");
        // Export proteins
        exportProteinsItem = new JMenuItem();
        exportProteinsItem.setText("Protein Results");
        exportProteinsItem.setEnabled(false);
        exportProteinsItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent evt) {
                exportResults(ResultExporter.ExportType.PROTEINS);
            }
        });
        exportMenu.add(exportProteinsItem);

        // Export peptides
        exportPeptidesItem = new JMenuItem();
        exportPeptidesItem.setText("Peptide Results");
        exportPeptidesItem.setEnabled(false);
        exportPeptidesItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent evt) {
                exportResults(ResultExporter.ExportType.PEPTIDES);
            }
        });
        exportMenu.add(exportPeptidesItem);

        // Export PSMs
        exportPSMsItem = new JMenuItem();
        exportPSMsItem.setText("PSM Results");
        exportPSMsItem.setEnabled(false);
        exportPSMsItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent evt) {
                exportResults(ResultExporter.ExportType.PSMS);
            }
        });
        exportMenu.add(exportPSMsItem);

        // Export Scores
        exportScoresItem = new JMenuItem();
        exportScoresItem.setText("Scoring Results");
        exportScoresItem.setEnabled(true);
        exportScoresItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent evt) {
                exportResults(ResultExporter.ExportType.SCORES);
            }
        });
        exportMenu.add(exportScoresItem);
        
        menuBar.add(exportMenu);

        // Help menu
        JMenu helpMenu = new JMenu();
        helpMenu.setText("Help");

        // About frame
        helpFrameItem = new JMenuItem();
        helpFrameItem.setText("Help Contents");
        helpFrameItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent evt) {
                helpTriggered();
            }
        });
        helpMenu.add(helpFrameItem);


        // About frame
        aboutFrameItem = new JMenuItem();
        aboutFrameItem.setText("About");
        aboutFrameItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent evt) {
                aboutTriggered();
            }
        });
        helpMenu.addSeparator();
        helpMenu.add(aboutFrameItem);
        menuBar.add(helpMenu);
    }

    /**
     * This methods setups the annotation menu bar.
     */
    private void setupAnnotationMenuBar() {
        annotationMenuBar = new JMenuBar();
        splitterMenu1 = new JMenu();
        splitterMenu2 = new JMenu();
        splitterMenu3 = new JMenu();
        splitterMenu4 = new JMenu();
        splitterMenu5 = new JMenu();
        splitterMenu6 = new JMenu();
        splitterMenu7 = new JMenu();
        ionsMenu = new JMenu();
        aIonCheckBoxMenuItem = new JCheckBoxMenuItem();
        bIonCheckBoxMenuItem = new JCheckBoxMenuItem();
        cIonCheckBoxMenuItem = new JCheckBoxMenuItem();        
        xIonCheckBoxMenuItem = new JCheckBoxMenuItem();
        yIonCheckBoxMenuItem = new JCheckBoxMenuItem();
        zIonCheckBoxMenuItem = new JCheckBoxMenuItem();
        precursorCheckMenuItem = new JCheckBoxMenuItem();
        reporterIonsCheckMenuItem = new JCheckBoxMenuItem();
        immoniumIonsCheckMenuItem = new JCheckBoxMenuItem();
        forwardIonsDeNovoCheckBoxMenuItem = new JCheckBoxMenuItem();
        rewindIonsDeNovoCheckBoxMenuItem = new JCheckBoxMenuItem();
        deNovoChargeOneJRadioButtonMenuItem = new JCheckBoxMenuItem();
        deNovoChargeTwoJRadioButtonMenuItem = new JCheckBoxMenuItem();
        allCheckBoxMenuItem = new JCheckBoxMenuItem();
        barsCheckBoxMenuItem = new JCheckBoxMenuItem();
        otherMenu = new JMenu();
        lossMenu = new JMenu();
        chargeMenu = new JMenu();
        deNovoMenu = new JMenu();
        settingsMenu = new JMenu();
        deNovoChargeButtonGroup = new ButtonGroup();
        annotationMenuBar.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        annotationMenuBar.setOpaque(false);


        splitterMenu1.setText("|");
        splitterMenu1.setEnabled(false);
        annotationMenuBar.add(splitterMenu1);


        ionsMenu.setText("Ions");
        ionsMenu.setEnabled(false);

        aIonCheckBoxMenuItem.setText("a");
        aIonCheckBoxMenuItem.setToolTipText("a-ions");
        aIonCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        ionsMenu.add(aIonCheckBoxMenuItem);

        bIonCheckBoxMenuItem.setText("b");
        bIonCheckBoxMenuItem.setSelected(true);
        bIonCheckBoxMenuItem.setToolTipText("b-ions");
        bIonCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        ionsMenu.add(bIonCheckBoxMenuItem);

        cIonCheckBoxMenuItem.setText("c");
        cIonCheckBoxMenuItem.setToolTipText("c-ions");
        cIonCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        ionsMenu.add(cIonCheckBoxMenuItem);

        xIonCheckBoxMenuItem.setText("x");
        xIonCheckBoxMenuItem.setToolTipText("x-ions");
        xIonCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        ionsMenu.add(xIonCheckBoxMenuItem);

        yIonCheckBoxMenuItem.setText("y");
        yIonCheckBoxMenuItem.setSelected(true);
        yIonCheckBoxMenuItem.setToolTipText("y-ions");
        yIonCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        ionsMenu.add(yIonCheckBoxMenuItem);

        zIonCheckBoxMenuItem.setText("z");
        zIonCheckBoxMenuItem.setToolTipText("z-ions");
        zIonCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        ionsMenu.add(zIonCheckBoxMenuItem);
        annotationMenuBar.add(ionsMenu);

        splitterMenu2.setText("|");
        splitterMenu2.setEnabled(false);
        annotationMenuBar.add(splitterMenu2);

        otherMenu.setText("Other");
        otherMenu.setEnabled(true);

        precursorCheckMenuItem.setSelected(true);
        precursorCheckMenuItem.setText("Precursor");
        precursorCheckMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        otherMenu.add(precursorCheckMenuItem);

        immoniumIonsCheckMenuItem.setSelected(true);
        immoniumIonsCheckMenuItem.setText("Immonium");
        immoniumIonsCheckMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        otherMenu.add(immoniumIonsCheckMenuItem);

//        reporterIonsCheckMenuItem.setSelected(true);
//        reporterIonsCheckMenuItem.setText("Reporter");
//        reporterIonsCheckMenuItem.addActionListener(new java.awt.event.ActionListener() {
//            public void actionPerformed(java.awt.event.ActionEvent evt) {
//                updateAnnotationPreferences();
//            }
//        });
//        otherMenu.add(reporterIonsCheckMenuItem);

        annotationMenuBar.add(otherMenu);

        splitterMenu3.setText("|");
        splitterMenu3.setEnabled(false);
        annotationMenuBar.add(splitterMenu3);

        lossMenu.setText("Loss");
        lossMenu.setEnabled(true);
        lossMenu.add(new JSeparator());

//        adaptCheckBoxMenuItem.setText("Adapt");
//        adaptCheckBoxMenuItem.setToolTipText("Adapt losses to sequence and modifications");
//        adaptCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
//            public void actionPerformed(java.awt.event.ActionEvent evt) {
//                updateAnnotationPreferences();
//            }
//        });
//        lossMenu.add(adaptCheckBoxMenuItem);

        annotationMenuBar.add(lossMenu);

        splitterMenu4.setText("|");
        splitterMenu4.setEnabled(false);
        annotationMenuBar.add(splitterMenu4);

        chargeMenu.setText("Charge");
        chargeMenu.setEnabled(true);
        annotationMenuBar.add(chargeMenu);

        splitterMenu5.setText("|");
        splitterMenu5.setEnabled(false);
        annotationMenuBar.add(splitterMenu5);

        deNovoMenu.setText("De Novo");

        forwardIonsDeNovoCheckBoxMenuItem.setText("f-ions");
        forwardIonsDeNovoCheckBoxMenuItem.setSelected(true);
        forwardIonsDeNovoCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        deNovoMenu.add(forwardIonsDeNovoCheckBoxMenuItem);

        rewindIonsDeNovoCheckBoxMenuItem.setText("r-ions");
        rewindIonsDeNovoCheckBoxMenuItem.setSelected(true);
        rewindIonsDeNovoCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        deNovoMenu.add(rewindIonsDeNovoCheckBoxMenuItem);
        deNovoMenu.add(new JSeparator());

        deNovoChargeButtonGroup.add(deNovoChargeOneJRadioButtonMenuItem);
        deNovoChargeOneJRadioButtonMenuItem.setSelected(true);
        deNovoChargeOneJRadioButtonMenuItem.setText("Single Charge");
        deNovoChargeOneJRadioButtonMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        deNovoMenu.add(deNovoChargeOneJRadioButtonMenuItem);

        deNovoChargeButtonGroup.add(deNovoChargeTwoJRadioButtonMenuItem);
        deNovoChargeTwoJRadioButtonMenuItem.setText("Double Charge");
        deNovoChargeTwoJRadioButtonMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        deNovoMenu.add(deNovoChargeTwoJRadioButtonMenuItem);

        annotationMenuBar.add(deNovoMenu);

        splitterMenu6.setText("|");
        splitterMenu6.setEnabled(false);
        annotationMenuBar.add(splitterMenu6);

        settingsMenu.setText("Settings");
        settingsMenu.setEnabled(true);

        allCheckBoxMenuItem.setText("Show All Peaks");
        allCheckBoxMenuItem.setToolTipText("Show all peaks or just the annotated peaks");
        allCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateAnnotationPreferences();
            }
        });
        settingsMenu.add(allCheckBoxMenuItem);
        annotationMenuBar.add(settingsMenu);

        splitterMenu7.setText("|");
        splitterMenu7.setEnabled(false);
        annotationMenuBar.add(splitterMenu7);
    }

    /**
     * Returns the annotation menu bar.
     *
     * @return the annotation menu bar
     */
    public JMenuBar getAnnotationMenuBar() {
        return annotationMenuBar;
    }

    /**
     * Constructs the input panel.
     */
    private void constructSearchPanel() {

        mainPnl = new JPanel(new FormLayout("l:p, 5dlu, l:p", "t:p"));
        searchPnl = new JPanel(new FormLayout("5dlu, l:p, 5dlu", "5dlu, p, 10dlu, p, 10dlu, p, 10dlu, p, 5dlu"));
        inputPnl = new JPanel(new FormLayout("5dlu, p, 10dlu, l:p, 10dlu, p, 5dlu, p, 5dlu", "10dlu, p, 10dlu"));
        inputPnl.setPreferredSize(new Dimension(650, 55));

        JXTitledPanel inputTtlPnl = PanelFactory.createTitledPanel("Input Files", inputPnl);

        // Spectra files label
        spectraFilesLbl = new JLabel("Spectrum File(s):");

        // Select button for browsing for the MGF files.
        addSpectraBtn = new JButton("Browse");
        addSpectraBtn.setToolTipText("Select spectra files");

        // Clear button for removing selected files
        clearSpectraBtn = new JButton("Clear");
        clearSpectraBtn.setToolTipText("Clear spectra files");
        clearSpectraBtn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                spectraFiles = new ArrayList<File>();
                spectraFilesTtf.setText(spectraFiles.size() + " file(s) selected");
                runPepNovoBtn.setEnabled(false);
                cancelBtn.setEnabled(false);
            }
        });

        // Chunked spectra files textfield
        spectraFilesTtf = new JTextField(32);
        spectraFilesTtf.setEditable(false);
        spectraFilesTtf.setMaximumSize(new Dimension(spectraFilesTtf.getMaximumSize().width, spectraFilesTtf.getPreferredSize().height));
        spectraFilesTtf.setText(spectraFiles.size() + " file(s) selected");

        addSpectraBtn.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                // First check whether a file has already been selected.
                // If so, start from that file's parent.
                File startLocation = new File(getJarFilePath() + File.separator + Constants.INPUT_FOLDER);
                if (spectraFiles.size() > 0) {
                    File temp = spectraFiles.get(0);
                    startLocation = temp.getParentFile();
                }
                JFileChooser fc = new JFileChooser(startLocation);
                fc.setFileFilter(new ExtensionFileFilter("mgf", false));
                fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
                fc.setMultiSelectionEnabled(true);
                int result = fc.showOpenDialog(MainFrame.this);
                if (result == JFileChooser.APPROVE_OPTION) {
                    File[] files = fc.getSelectedFiles();
                    for (int i = 0; i < files.length; i++) {
                        if (files[i].isDirectory()) {
                            File[] currentFiles = files[i].listFiles();
                            for (int k = 0; k < currentFiles.length; k++) {
                                if (fc.getFileFilter().accept(currentFiles[k])) {
                                    spectraFiles.add(currentFiles[k]);
                                }
                            }
                        } else {
                            spectraFiles.add(files[i]);
                        }
                    }
                    spectraFilesTtf.setText(spectraFiles.size() + " file(s) selected");
                    filename = spectraFiles.get(0).getName();
                    // Set back the progress bar..
                    searchPrg.setValue(0);
                }
                setButtonConfiguration();
            }
        });
        inputPnl.add(spectraFilesLbl, cc.xy(2, 2));
        inputPnl.add(spectraFilesTtf, cc.xy(4, 2));
        inputPnl.add(addSpectraBtn, cc.xy(6, 2));
        inputPnl.add(clearSpectraBtn, cc.xy(8, 2));

        // Add the input panel to the search panel
        searchPnl.add(inputTtlPnl, cc.xy(2, 2));

        exeFolderPnl = new JPanel(new FormLayout("5dlu, 60dlu, 5dlu, l:p, 10dlu, p, 5dlu, f:p:g, 5dlu", "10dlu, p, 10dlu"));
        exeFolderPnl.setPreferredSize(new Dimension(650, 55));
        JXTitledPanel exeFolderTtlPnl = PanelFactory.createTitledPanel("Executable Folder", exeFolderPnl);

        // Pepnovo location label
        pepnovoLbl = new JLabel("PepNovo Location:");

        // Pepnovo executable folder textfield
        pepnovoTtf = new JTextField(38);
        pepnovoTtf.setEditable(false);
        pepnovoTtf.setMaximumSize(new Dimension(pepnovoTtf.getMaximumSize().width, pepnovoTtf.getPreferredSize().height));

        // Select button for browsing for the PepNovo+ folder.
        browsePepnovoBtn = new JButton("Browse");
        browsePepnovoBtn.setToolTipText("Select PepNovo+ executable folder");
        browsePepnovoBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                browsePepnovoFolder();
                setButtonConfiguration();
            }
        });

        exeFolderPnl.add(pepnovoLbl, cc.xy(2, 2));
        exeFolderPnl.add(pepnovoTtf, cc.xy(4, 2));
        exeFolderPnl.add(browsePepnovoBtn, cc.xy(6, 2));

        // Add the pepnovo panel to the search panel
        searchPnl.add(exeFolderTtlPnl, cc.xy(2, 4));

        outputPnl = new JPanel(new FormLayout("5dlu, 60dlu, 5dlu, l:p, 10dlu, p, 5dlu, f:p:g, 5dlu", "10dlu, p, 10dlu"));
        outputPnl.setPreferredSize(new Dimension(650, 55));
        JXTitledPanel outputTtlPnl = PanelFactory.createTitledPanel("Output Folder", outputPnl);

        // Output location label
        outputLbl = new JLabel("Output Location:");

        // Output textfield
        outputTtf = new JTextField(38);
        outputTtf.setEditable(false);
        outputTtf.setMaximumSize(new Dimension(outputTtf.getMaximumSize().width, outputTtf.getPreferredSize().height));

        // Select button for browsing for the output folder.
        browseOutputBtn = new JButton("Browse");
        browseOutputBtn.setToolTipText("Select output folder");
        browseOutputBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                browseOutputFolder();
                setButtonConfiguration();
            }
        });

        outputPnl.add(outputLbl, cc.xy(2, 2));
        outputPnl.add(outputTtf, cc.xy(4, 2));
        outputPnl.add(browseOutputBtn, cc.xy(6, 2));

        // Add the output panel to the search panel
        searchPnl.add(outputTtlPnl, cc.xy(2, 6));

        // Add the log panel.
        logPnl = new LogPanel(true);
        JXTitledPanel logTtlPnl = PanelFactory.createTitledPanel("Logging", logPnl);

        logPnl.setPreferredSize(new Dimension(Constants.LOGPANEL_WIDTH, Constants.LOGPANEL_HEIGHT));
        searchPnl.add(logTtlPnl, cc.xy(2, 8));

        mainPnl.add(searchPnl, cc.xy(1, 1));
        mainPnl.add(denovoParamsPnl, cc.xy(3, 1));
    }

    /**
     * This method checks whether the start configuration is valid or not.
     * @return Boolean if the start configuration is valid.
     */
    public boolean checkForValidConfiguration() {
        boolean appFolderValid = checkPepnovoFolder(new File(pepnovoTtf.getText()));

        if(appFolderValid && spectraFiles.size() > 0 && outputTtf.getText().length() > 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * This method is called when the Browse button for Pepnovo is pressed.
     */
    public void browsePepnovoFolder() {
        File startLocation = new File(getJarFilePath() + File.separator + Constants.PEPNOVO_FOLDER);
        if (pepnovoTtf != null && pepnovoTtf.getText() != null && !pepnovoTtf.getText().trim().equals("")) {
            File temp = new File(pepnovoTtf.getText());
            if (temp.exists() && temp.isDirectory()) {
                startLocation = temp;
            }
        }
        JFileChooser fc = new JFileChooser(startLocation);
        fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        int result = fc.showOpenDialog(this);
        if (result == JFileChooser.APPROVE_OPTION) {
            File file = fc.getSelectedFile();
            lastSelected = file.getAbsolutePath();
            if (checkPepnovoFolder(file)) {
                pepnovoTtf.setText(file.getAbsolutePath());
            } else {
                JOptionPane.showMessageDialog(this, new String[]{"Incorrect PepNovo folder specified.", "Please try again, or press cancel to exit."}, "Incorrect PepNovo Folder", JOptionPane.WARNING_MESSAGE);
                browsePepnovoFolder();
            }
            startLocation = file;
        }
    }

    /**
     * Returns the path to the jar file.
     *
     * @return the path to the jar file
     */
    protected String getJarFilePath() {
        
        String path = this.getClass().getResource("MainFrame.class").getPath();

        if (path.indexOf("/" + Constants.APPTITLE.toLowerCase() + "-") != -1) {
            path = path.substring(5, path.lastIndexOf("/" + Constants.APPTITLE.toLowerCase() + "-"));
            path = path.replace("%20", " ");
            path = path.replace("%5b", "[");
            path = path.replace("%5d", "]");

            if (System.getProperty("os.name").lastIndexOf("Windows") != -1) {
                path = path.replace("/", "\\");
            }
        } else {
            path = ".";
        }
        return path;
    }

    /**
     * The method that builds the about dialog.
     */
    private void showAbout() {
        StringBuffer tMsg = new StringBuffer();
        tMsg.append("Version: " + Constants.APPTITLE + " " + new tagdb.util.Properties().getVersion());
        tMsg.append("\n");
        tMsg.append("\n");
        tMsg.append("This software is being developed by Thilo Muth and Harald Barsnes.");
        tMsg.append("\n");
        tMsg.append("\n");
        tMsg.append("The latest version is available at http://tag-db.googlecode.com");
        tMsg.append("\n");
        tMsg.append("\n");
        tMsg.append("If any questions arise, contact the corresponding author: ");
        tMsg.append("\n");
        tMsg.append("muth@mpi-magdeburg.mpg.de");
        tMsg.append("\n");
        tMsg.append("\n");
        tMsg.append("");
        tMsg.append("");
        JOptionPane.showMessageDialog(this, tMsg,
                "About " + Constants.APPTITLE, JOptionPane.INFORMATION_MESSAGE);
    }

    /**
     * This method is called when choosing which results folder to use.
     */
    private void browseOutputFolder() {
        File startLocation = new File(getJarFilePath() + File.separator + Constants.OUTPUT_FOLDER);
        if (outputTtf.getText() != null && !outputTtf.getText().trim().equals("")) {
            File temp = new File(outputTtf.getText());
            if (temp.isDirectory()) {
                startLocation = temp;
            } else {
                startLocation = temp.getParentFile();
            }
        }
        JFileChooser fc = new JFileChooser(startLocation);
        fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        fc.setMultiSelectionEnabled(false);
        int result = fc.showOpenDialog(this);
        if (result == JFileChooser.APPROVE_OPTION) {
            outputFolder = fc.getSelectedFile();
            outputTtf.setText(outputFolder.getAbsolutePath());
            lastSelected = outputFolder.getAbsolutePath();
        }

    }

    /**
     * This method sets the button configuration.
     */
    public void setButtonConfiguration() {
        if(checkForValidConfiguration()) {
            runPepNovoBtn.setEnabled(true);
            cancelBtn.setEnabled(false);
        } else {
            runPepNovoBtn.setEnabled(false);
            cancelBtn.setEnabled(false);
        }
    }

    /**
     * This method checks for the folder being the Pepnovo folder.
     *
     * @param file File with the Pepnovo folder
     * @return boolean to show whether the Pepnovo folder is correct.
     */
    public boolean checkPepnovoFolder(File file) {
        boolean result = false;
        if (file.exists() && file.isDirectory()) {
            String[] fileNames = file.list();
            int count = 0;
            for (int i = 0; i < fileNames.length; i++) {
                String lFileName = fileNames[i];
                if (lFileName.startsWith("PepNovo")) {
                    count++;
                }
            }

            if (count > 0) {
                result = true;
            }
        }
        return result;
    }

    /**
     * Update the annotation menu bar with the current annotation preferences.
     *
     * @param precursorCharge The charge of the precursor.
     */
    public void updateAnnotationMenus(int precursorCharge) {
        
        ArrayList<String> selectedLosses = new ArrayList<String>();

        for (JCheckBoxMenuItem lossMenuItem : lossMenus.values()) {

            if (lossMenuItem.isSelected()) {
                selectedLosses.add(lossMenuItem.getText());
            }

            lossMenu.remove(lossMenuItem);
        }
        
        lossMenu.setVisible(true);
        //lossSplitter.setVisible(true);
        lossMenus.clear();
        
        HashMap<String, NeutralLoss> neutralLosses = new HashMap<String, NeutralLoss>();
        
        for (NeutralLoss neutralLoss : IonFactory.getInstance().getDefaultNeutralLosses()) {
            neutralLosses.put(neutralLoss.name, neutralLoss);
        }
        
//        for (ModificationMatch modMatch : peptide.getModificationMatches()) {
//            PTM ptm = ptmFactory.getPTM(modMatch.getTheoreticPtm());
//            for (NeutralLoss neutralLoss : ptm.getNeutralLosses()) {
//                neutralLosses.put(neutralLoss.name, neutralLoss);
//            }
//        }

        ArrayList<String> names = new ArrayList<String>(neutralLosses.keySet());
        Collections.sort(names);

        ArrayList<String> finalSelectedLosses = selectedLosses;
        boolean selected;

        if (names.isEmpty()) {
            lossMenu.setEnabled(false);
            //lossSplitter.setVisible(false);
        } else {
            for (int i = 0; i < names.size(); i++) {

                if (annotationPreferences.areNeutralLossesSequenceDependant()) {
                    selected = false;
                    for (NeutralLoss neutralLoss : annotationPreferences.getNeutralLosses().getAccountedNeutralLosses()) {
                        if (neutralLoss.isSameAs(neutralLoss)) {
                            selected = true;
                            break;
                        }
                    }
                } else {
                    if (finalSelectedLosses.contains(names.get(i))) {
                        selected = true;
                    } else {
                        selected = false;
                    }
                }

                JCheckBoxMenuItem lossMenuItem = new JCheckBoxMenuItem(names.get(i));
                lossMenuItem.setSelected(selected);
                lossMenuItem.addActionListener(new java.awt.event.ActionListener() {

                    public void actionPerformed(java.awt.event.ActionEvent evt) {
                        annotationPreferences.useAutomaticAnnotation(false);
                        annotationPreferences.setNeutralLossesSequenceDependant(false);
                        updateAnnotationPreferences();
                    }
                });
                lossMenus.put(neutralLosses.get(names.get(i)), lossMenuItem);
                lossMenu.add(lossMenuItem, i);
            }
        }
        
        ArrayList<String> selectedCharges = new ArrayList<String>();

        for (JCheckBoxMenuItem chargeMenuItem : chargeMenus.values()) {

            if (chargeMenuItem.isSelected()) {
                selectedCharges.add(chargeMenuItem.getText());
            }

            chargeMenu.remove(chargeMenuItem);
        }

        chargeMenus.clear();

        if (precursorCharge == 1) {
            precursorCharge = 2;
        }

        final ArrayList<String> finalSelectedCharges = selectedCharges;

        for (int charge = 1; charge < precursorCharge; charge++) {

            JCheckBoxMenuItem chargeMenuItem = new JCheckBoxMenuItem(charge + "+");

            if (annotationPreferences.useAutomaticAnnotation()) {
                chargeMenuItem.setSelected(annotationPreferences.getValidatedCharges().contains(charge));
            } else {
                if (finalSelectedCharges.contains(charge + "+")) {
                    chargeMenuItem.setSelected(true);

                } else {
                    chargeMenuItem.setSelected(false);
                }
            }

            chargeMenuItem.addActionListener(new java.awt.event.ActionListener() {

                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    annotationPreferences.useAutomaticAnnotation(false);
                    updateAnnotationPreferences();
                }
            });

            chargeMenus.put(charge, chargeMenuItem);
            chargeMenu.add(chargeMenuItem);
        }
    }

    /**
     * Save the current annotation preferences selected in the annotation menus.
     */
    public void updateAnnotationPreferences() {
        
        annotationPreferences.clearIonTypes();
        if (aIonCheckBoxMenuItem.isSelected()) {
            annotationPreferences.addIonType(IonType.PEPTIDE_FRAGMENT_ION, PeptideFragmentIon.A_ION);
        }
        if (bIonCheckBoxMenuItem.isSelected()) {
            annotationPreferences.addIonType(IonType.PEPTIDE_FRAGMENT_ION, PeptideFragmentIon.B_ION);
        }
        if (cIonCheckBoxMenuItem.isSelected()) {
            annotationPreferences.addIonType(IonType.PEPTIDE_FRAGMENT_ION, PeptideFragmentIon.C_ION);
        }
        if (xIonCheckBoxMenuItem.isSelected()) {
            annotationPreferences.addIonType(IonType.PEPTIDE_FRAGMENT_ION, PeptideFragmentIon.X_ION);
        }
        if (yIonCheckBoxMenuItem.isSelected()) {
            annotationPreferences.addIonType(IonType.PEPTIDE_FRAGMENT_ION, PeptideFragmentIon.Y_ION);
        }
        if (zIonCheckBoxMenuItem.isSelected()) {
            annotationPreferences.addIonType(IonType.PEPTIDE_FRAGMENT_ION, PeptideFragmentIon.Z_ION);
        }
        if (precursorCheckMenuItem.isSelected()) {
            annotationPreferences.addIonType(IonType.PRECURSOR_ION);
        }
        if (immoniumIonsCheckMenuItem.isSelected()) {
            annotationPreferences.addIonType(IonType.IMMONIUM_ION);
        }

        annotationPreferences.clearNeutralLosses();

        for (NeutralLoss neutralLoss : lossMenus.keySet()) {
            if (lossMenus.get(neutralLoss).isSelected()) {
                annotationPreferences.addNeutralLoss(neutralLoss);
            }
        }

        annotationPreferences.clearCharges();

        for (int charge : chargeMenus.keySet()) {
            if (chargeMenus.get(charge).isSelected()) {
                annotationPreferences.addSelectedCharge(charge);
            }
        }

        annotationPreferences.setShowAllPeaks(allCheckBoxMenuItem.isSelected());
        //annotationPreferences.setShowBars(barsCheckBoxMenuItem.isSelected());

        annotationPreferences.setShowForwardIonDeNovoTags(forwardIonsDeNovoCheckBoxMenuItem.isSelected());
        annotationPreferences.setShowRewindIonDeNovoTags(rewindIonsDeNovoCheckBoxMenuItem.isSelected());

        if (deNovoChargeOneJRadioButtonMenuItem.isSelected()) {
            annotationPreferences.setDeNovoCharge(1);
        } else {
            annotationPreferences.setDeNovoCharge(2);
        }

        // Refresh the spectrum panel
        resultPnl.refreshSpectrumPanel();
        tagSearchResultPnl.refreshSpectrumPanel();
    }

    /**
     * This method starts the de novo search when the start button is pressed.
     */
    public void searchBtnPressed() {
        searchTask = new SearchTask();
        searchTask.execute();
    }

    /**
     * This method cancels the de novo search when the cancel button is pressed.
     */
    public void cancelBtnPressed() {
        CancelTask cancelTask = new CancelTask();
        cancelTask.execute();
    }

    /**
     * openFastaFileAction
     *
     * @return OpenFileAction
     */
    private Action openFastaFileAction() {
        JFileChooser fc = new JFileChooser(new File(getJarFilePath() + File.separator + Constants.FASTA_FOLDER));
        JFrame loadFrame = new JFrame();
        return new OpenFastaFileAction(loadFrame, fc);
    }

    /**
     * This action class creates and shows an open-file dialog.
     */
    public class OpenFastaFileAction extends AbstractAction {

        private JFrame frame;
        private JFileChooser chooser;
        private FastaFileFilter filter = new FastaFileFilter();
        private File file = null;
        private String name = null;

        OpenFastaFileAction(JFrame frame, JFileChooser chooser) {
            super("Open Fasta File...");
            this.chooser = chooser;
            this.frame = frame;
        }

        public void actionPerformed(ActionEvent evt) {
            chooser.setFileFilter(filter);
            chooser.showOpenDialog(frame);
            // Get the selected file
            file = chooser.getSelectedFile();
            // Only get the path, if a file was selected
            if (file != null) {
                name = file.getAbsolutePath();
                fastaDbTtf.setText(name);
            }
        }
    }

    /**
     * This class represents the file for the FASTA file extension.
     */
    private static class FastaFileFilter extends javax.swing.filechooser.FileFilter {

        public boolean accept(File f) {
            return f.isDirectory()
                    || f.getName().toLowerCase().endsWith(".fasta");
        }

        public String getDescription() {
            return "Protein FASTA database (*" + ".fasta" + ")";
        }
    }

    @SuppressWarnings("rawtypes")
    private class SearchTask extends SwingWorker {
        private int max;

        protected Object doInBackground() throws Exception {
            try {
                runPepNovoBtn.setEnabled(false);
                cancelBtn.setEnabled(true);
                outputFiles = new ArrayList<String>();
                searchPrg.setStringPainted(true);
                searchPrg.setValue(0);
                // Write the modifications file
                ModificationFile.writeFile(pepnovoTtf.getText() + File.separator + "Models" + File.separator + "PepNovo_PTMs.txt", mods);

                for (int i = 0; i < spectraFiles.size(); i++) {
                    MascotGenericFileReader reader = new MascotGenericFileReader(spectraFiles.get(i));
                    max = reader.getFileNumber();
                    searchPrg.setMaximum(max);
                    params = collectDenovoParameters();
                    job = new PepnovoJob(new File(pepnovoTtf.getText()), spectraFiles.get(i), outputTtf.getText(), params);
                    Vector<MascotGenericFile> spectrumFiles = reader.getSpectrumFiles();
                    for (MascotGenericFile mgf : spectrumFiles) {
                        querySpectra.put(mgf.getTitle().trim(), mgf);
                    }
                    outputFiles.add(job.getOutputFilePath());

                    job.addPropertyChangeListener(new PropertyChangeListener() {
                        @Override
                        public void propertyChange(PropertyChangeEvent evt) {
                            if (evt.getPropertyName().equalsIgnoreCase("progress")) {
                                int progress = (Integer) evt.getNewValue();

                                // Update the progress bar.
                                searchPrg.setValue(progress);

                                // Finish the worker.
                                if (progress == max) {
                                    finished();
                                }
                            }
                        }
                    });
                    job.execute();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }

        /**
         * Returns the result when the PepNovo+ has finished.
         */
        public void finished() {
            JOptionPane.showMessageDialog(frame, 
                    "The de novo search has finished.\n"
                    + "See the De Novo Results tab for details.", 
                    "Search Successful", JOptionPane.INFORMATION_MESSAGE);
            tabPane.setSelectedIndex(2);
            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            spectraFilesTtf.setText("0 file(s) selected.");

            // Empty the spectrum files.
            spectraFiles.clear();

            // Disable the start and cancel buttons
            runPepNovoBtn.setEnabled(true);
            cancelBtn.setEnabled(false);

            // Start the parser task
            parseTask = new ParseTask(true);
            parseTask.execute();
        }
    }

    @SuppressWarnings("rawtypes")
    private class CancelTask extends SwingWorker {

        protected Object doInBackground() throws Exception {
            try {
                // Cancel the job.
                job.cancel();
                outputFiles = new ArrayList<String>();

                // finished
                finished();

            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }
        /**
         * Returns the result when the ParserTask has finished.
         */
        public void finished() {
            searchPrg.setStringPainted(true);
            searchPrg.setValue(0);
            JOptionPane.showMessageDialog(frame,
                    "The de novo search has been canceled.",
                    "Search Canceled", JOptionPane.INFORMATION_MESSAGE);
            setButtonConfiguration();
        }
    }

    @SuppressWarnings("rawtypes")
    private class ParseTask extends SwingWorker {

        /**
         * Flag for running a (new) de novo search before or loading a processed file.
         */
        private boolean isSearch;

        private ParseTask(boolean isSearch) {
            this.isSearch = isSearch;
        }

        protected Object doInBackground() throws Exception {
            try {
                setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                for (int i = 0; i < outputFiles.size(); i++) {

                    // Get the denovo result
                    DenovoResult denovoResult = DenovoParser.read(outputFiles.get(i));

                    // Get all the entries
                    List<DenovoEntry> entryList = denovoResult.getEntryList();

                    // Iterate the entries.
                    for (DenovoEntry entry : entryList) {
                        // Fill the results map
                        String spectrumName = entry.getSpectrumName().trim();
                        resultsMap.put(spectrumName, entry.getHits());
                        // TODO: Parameterize tag generation.
                        TagGenerator tagGen = new TagGenerator(entry.getHits(), querySpectra.get(spectrumName), 3, 3);
                        // TODO: Use both tags.

                        tagMap.put(spectrumName, tagGen.getAllTags());

                    }
                }
                finished();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }

        /**
         * Returns the result when the ParserTask has finished.
         */
        public void finished() {

            // Update de novo results.
            denovoSearchResult = new DenovoSearchResult(querySpectra, resultsMap, tagMap, filename);

            resultPnl.updateResults(denovoSearchResult);
            statsPnl.updateDenovoResults(denovoSearchResult);
            enableAnnotationMenuBar();

            // Empty the output files.
            outputFiles.clear();

            if(!isSearch) {
                JOptionPane.showMessageDialog(frame, "Loaded Output File.", "Load Successful", JOptionPane.INFORMATION_MESSAGE);
            }
            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

            // Activate the tabs
            tabPane.setEnabledAt(1, true);
            tabPane.setEnabledAt(2, true);
            tabPane.setEnabledAt(4, true);

            tabPane.setSelectedIndex(1);
        }

    }

    /**
     * Applies the denovo search filter to the results.
     */
    public void setDenovoSearchFilter(DenovoSearchFilter filter) {
        resultPnl.refreshFilteredSpectraTable(filter);
    }

    /**
     * Enables the annotation menu bar.
     */
    private void enableAnnotationMenuBar() {
        ionsMenu.setEnabled(true);
    }

    @SuppressWarnings("rawtypes")
    private class TagSearchTask extends SwingWorker {
        private int max;
        boolean suspectHitsFound = false;

        protected Object doInBackground() throws Exception {
            try {
                setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                runTagSearchBtn.setEnabled(false);

                SearchParams params = collectSearchParameters();
                TagSearch tagSearch = new TagSearch(logPnl2, new Database(fastaDbTtf.getText()), denovoSearchResult, params);
                max = denovoSearchResult.size();
                searchPrg2.setMaximum(max - 1);

                tagSearch.addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        if (evt.getPropertyName().equalsIgnoreCase("progress")) {
                            int progress = (Integer) evt.getNewValue();

                            // Update the progress bar.
                            searchPrg2.setValue(progress);
                        }
                    }
                });

                // Search for hits
                List<SuspectHit> suspectHits = tagSearch.searchHits();

                // Check if any suspect hits were found at all
                if(suspectHits.size() > 0) {
                    tagSearchResult = new TagSearchResult(filename);
                    suspectHitsFound = true;
                    for (SuspectHit hit : suspectHits) {
                        PeptideMatcher matcher = new PeptideMatcher(hit, querySpectra.get(hit.getSpectrumTitle().trim()), params);

                        // Get the peptide spectrum match
                        PeptideSpectrumMatch psm = matcher.findMatch();
                        if(psm != null){
                            tagSearchResult.addPeptideSpectrumMatch(psm);
                        }


                        // Create a peptide hit
                        PeptideHit peptideHit = new PeptideHit(hit.getPeptide().getSequence(), psm);

                        // Create a protein hit
                        Protein protein = hit.getProtein();

                        Header header = SequenceFactory.getInstance().getHeader(protein.getAccession());

                        ProteinHit proteinHit = new ProteinHit(header, protein.getSequence(), peptideHit);
                        tagSearchResult.addProtein(proteinHit);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            finished(suspectHitsFound);
            return 0;
        }

        /**
         * Returns the result when Fasts has finished.
         */
        public void finished(boolean success) {
            if(success) {
                tagSearchResultPnl.updateResults(tagSearchResult);
                statsPnl.updateTagSearchResults(tagSearchResult);
                JOptionPane.showMessageDialog(frame,
                        "The tag database search has finished.\n"
                                + "See the Tag-DB Search Results tab for details.",
                        "Search Successful", JOptionPane.INFORMATION_MESSAGE);

                runTagSearchBtn.setEnabled(true);

                // Enable the results tab.
                tabPane.setEnabledAt(3, true);
                tabPane.setSelectedIndex(3);
                // Enable the exporter
                setExportResultsEnabled(true);
            } else {
                JOptionPane.showMessageDialog(frame,
                        "The tag database search has finished, but no identifications were found.\n"
                                + "Please try to run with other settings or database.",
                        "No hits found!", JOptionPane.WARNING_MESSAGE);
                runTagSearchBtn.setEnabled(true);

            }
            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }
    }

    /**
     * Executes the tag search task when the search button is pressed.
     */
    private void runSearchBtnPressed() {
        TagSearchTask task = new TagSearchTask();
        task.execute();
    }

    /**
     * This method collects the denovo paramters.
     *
     * @return The parameters for the denovo search.
     */
    public DenovoParams collectDenovoParameters() {
        DenovoParams tempParams = new DenovoParams();
        tempParams.setEnzyme(enzymesCbx.getSelectedItem().toString());
        tempParams.setModel(modelCbx.getSelectedItem().toString());
        tempParams.setFragmentTol((Double) fragTolSpn.getValue());
        tempParams.setPrecursorTol((Double) precTolSpn.getValue());
        tempParams.setNumHits((Integer) numSolutionsSpn.getValue());
        tempParams.setUseSpectrumCharge(spectrumChargeCbx.isSelected());
        tempParams.setUseSpectrumMz(spectrumMzCbx.isSelected());
        tempParams.setFilterLowQualilySpectra(lowQualFilterCbx.isSelected());
        StringBuilder tempMods = new StringBuilder();

        Map<String, String> modIdMap = ModificationFile.getModIdMap();

        // Append the selected modifications
        for (String mod : selectedMods) {
            tempMods.append(modIdMap.get(mod));
            tempMods.append(":");
        }

        tempMods.deleteCharAt(tempMods.length() - 1);    // remove final delimiter character
        tempParams.setMods(tempMods.toString());
        return tempParams;
    }

    /**
     * Returns the de novo search parameters.
     * @return De novo search parameters.
     */
    public DenovoParams getDenovoParameters() {
        return params;
    }

    /**
     * This method collects tag database search paramters.
     *
     * @return The parameters for tag database search.
     */
    public SearchParams collectSearchParameters() {
        return new SearchParams((Double) precTolSpn2.getValue(), (Double) fragTolSpn2.getValue(), (Integer) missedCleavagesSpn.getValue());
    }

    /**
     * Action dialog for opening a PepNovo output file.
     */
    private void openFileTriggered() {
        new LoadDialog(this, true);
    }

    /**
     * This method opens the processed output file.
     *
     * @param outputPath The path to the output
     * @param spectrumPath The path to the spectrum
     */
    public void openOutputFile(String outputPath, String spectrumPath) {
        spectraFiles = new ArrayList<File>();
        spectraFiles.add(new File(spectrumPath));
        filename = spectraFiles.get(0).getName();
        // TODO: spectrumFactory = SpectrumFactory.getInstance();

        MascotGenericFileReader reader;
        try {
            // TODO: spectrumFactory.addSpectra(new File(spectrumPath));

            reader = new MascotGenericFileReader(spectraFiles.get(0));
            Vector<MascotGenericFile> spectrumFiles = reader.getSpectrumFiles();

            for (MascotGenericFile mgf : spectrumFiles) {
                //TODO: spectrumFactory.getSpectrum()
                querySpectra.put(mgf.getTitle().trim(), mgf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        outputFiles = new ArrayList<String>();
        outputFiles.add(outputPath);

        // TODO: These parameters should be derived from a loaded settings file.
        params = collectDenovoParameters();

        // Start the parser task
        parseTask = new ParseTask(false);
        parseTask.execute();
    }

    /**
     * Select one config file for load.
     *
     * @param aFrame
     * @param aOkayButtonText
     * @return the selected file as a string
     */
    public String selectForLoad(JFrame aFrame, String aOkayButtonText) {
        String tBack = null;

        JFileChooser iChooser = new JFileChooser(new File(lastSelected));
        iChooser.setFileFilter(new OutputFileFilter());
        iChooser.setMultiSelectionEnabled(false);
        iChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        iChooser.setApproveButtonText(aOkayButtonText);

        if (iChooser.showOpenDialog(aFrame) == JFileChooser.APPROVE_OPTION) {
            File tSelectedFile;
            tSelectedFile = iChooser.getSelectedFile();
            if (tSelectedFile != null) {
                if (tSelectedFile.canRead() && tSelectedFile.exists()) {
                    tBack = tSelectedFile.toString();
                } else {
                    JOptionPane.showMessageDialog(aFrame, "The file does not exist or is not readable.",
                            "Selection Error", JOptionPane.ERROR_MESSAGE);
                }
            }
        }
        return tBack;
    }

    /**
     * The file filter for denovo file.
     */
    public static class OutputFileFilter extends FileFilter {

        @Override
        public boolean accept(File aFile) {
            return aFile.isDirectory() || aFile.getName().toLowerCase().endsWith(".out");
        }

        @Override
        public String getDescription() {
            return "PepNovo+ Output File (*" + ".out" + ")";
        }
    }

    /**
     * Exports the results.
     * @param exportType The export type
     */
    private void exportResults(ResultExporter.ExportType exportType) {
        JFileChooser chooser = new JFileChooser(lastSelectedFolder);
        chooser.setFileFilter(new CsvFileFilter());
        chooser.setMultiSelectionEnabled(false);
        String type = "";
        
        switch (exportType) {
            case PROTEINS:
                type = "Proteins";
                break;
            case PEPTIDES:
                type = "Peptides";
                break;
            case PSMS:
                type = "PSMs";
                break;
            case SCORES:
                type = "Scores";
                break;
        }
        
        chooser.setDialogTitle("Export " + type);
        File selectedFile;

        int returnVal = chooser.showSaveDialog(this);
        if (returnVal == JFileChooser.APPROVE_OPTION) {

            selectedFile = chooser.getSelectedFile();

            if (!selectedFile.getName().toLowerCase().endsWith(".csv")) {
                selectedFile = new File(selectedFile.getAbsolutePath() + ".csv");
            }

            while (selectedFile.exists()) {
                int option = JOptionPane.showConfirmDialog(this, "The  file " + chooser.getSelectedFile().getName()
                        + " already exists. Replace file?", "Replace File?", JOptionPane.YES_NO_CANCEL_OPTION);

                if (option == JOptionPane.NO_OPTION) {
                    chooser = new JFileChooser(lastSelectedFolder);
                    chooser.setFileFilter(new CsvFileFilter());
                    chooser.setMultiSelectionEnabled(false);
                    chooser.setDialogTitle("Export proteins");
                    returnVal = chooser.showSaveDialog(this);

                    if (returnVal == JFileChooser.CANCEL_OPTION) {
                        return;
                    } else {
                        selectedFile = chooser.getSelectedFile();

                        if (!selectedFile.getName().toLowerCase().endsWith(".csv")) {
                            selectedFile = new File(selectedFile.getAbsolutePath() + ".csv");
                        }
                    }
                } else { // YES option
                    break;
                }
            }
            this.setCursor(new Cursor(Cursor.WAIT_CURSOR));

            try {
                selectedFile = chooser.getSelectedFile();

                if (!selectedFile.getName().toLowerCase().endsWith(".csv")) {
                    selectedFile = new File(selectedFile.getAbsolutePath() + ".csv");
                }

                if (selectedFile.exists()) {
                    selectedFile.delete();
                }
                selectedFile.createNewFile();

                switch (exportType) {
                    case PROTEINS:
                        ResultExporter.exportProteins(selectedFile.getPath(), tagSearchResult);
                        break;
                    case PEPTIDES:
                        ResultExporter.exportPeptides(selectedFile.getPath(), tagSearchResult);
                        break;
                    case PSMS:
                        ResultExporter.exportPSMs(selectedFile.getPath(), tagSearchResult);
                        break;
                    case SCORES:
                        ResultExporter.exportScores(selectedFile.getPath(), resultsMap);
                        break;
                    default:
                        break;
                }

                lastSelectedFolder = selectedFile.getPath();

            } catch (IOException ex) {
                JOptionPane.showMessageDialog(this,
                        "An error occurred when exporting the +" + type.toUpperCase() + ".",
                        "Error Exporting " + type,
                        JOptionPane.ERROR_MESSAGE);
                ex.printStackTrace();
            }
            JOptionPane.showMessageDialog(this, "Successfully exported the " + type + " to the file:\n" + selectedFile.getAbsolutePath() + ".", "Export Successful!",
                    JOptionPane.INFORMATION_MESSAGE);
            this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
        }
    }

    /**
     * Enables the export functionalities.
     * @param enabled The state of the export menu items.
     */
    public void setExportResultsEnabled(boolean enabled){
        exportPSMsItem.setEnabled(enabled);
        exportPeptidesItem.setEnabled(enabled);
        exportProteinsItem.setEnabled(enabled);
    }

    /**
     * The method that builds the about frame.
     */
    private void aboutTriggered() {
        setCursor(new Cursor(java.awt.Cursor.WAIT_CURSOR));
        new HtmlFrame(this, getClass().getResource("/html/about.html"), "About");
        setCursor(new Cursor(java.awt.Cursor.DEFAULT_CURSOR));
    }

    /**
     * The method that builds the help frame.
     */
    private void helpTriggered() {
        setCursor(new Cursor(java.awt.Cursor.WAIT_CURSOR));
        new HtmlFrame(this, getClass().getResource("/html/help.html"), "Help");
        setCursor(new Cursor(java.awt.Cursor.DEFAULT_CURSOR));
    }

    /**
     * Returns tag search result.
     *
     * @return The TagSearchResult object.
     */
    public TagSearchResult getSearchResult() {
        return tagSearchResult;
    }

    /**
     * Returns query spectra.
     *
     * @return The query spectra.
     */
    public Map<String, MascotGenericFile> getQuerySpectra() {
        return querySpectra;
    }

    /**
     * Returns the annotation preferences.
     * @return The annotation preferences.
     */
    public AnnotationPreferences getAnnotationPreferences() {
        return annotationPreferences;
    }

    /**
     * Ask the user if he/she wants to add a shortcut at the desktop.
     */
    private void addShortcutAtDeskTop() {

        String jarFilePath = getJarFilePath();
        String versionNumber = new tagdb.util.Properties().getVersion();

        if (!jarFilePath.equalsIgnoreCase(".")) {

            // remove the initial '/' at the start of the line
            if (jarFilePath.startsWith("\\") && !jarFilePath.startsWith("\\\\")) {
                jarFilePath = jarFilePath.substring(1);
            }

            String iconFileLocation = jarFilePath + "\\conf\\tag-db.ico";
            String jarFileLocation = jarFilePath + "\\" + Constants.APPTITLE.toLowerCase() + "-" + versionNumber + ".jar";

            try {
                JShellLink link = new JShellLink();
                link.setFolder(JShellLink.getDirectory("desktop"));
                link.setName(Constants.APPTITLE + " " + versionNumber);
                link.setIconLocation(iconFileLocation);
                link.setPath(jarFileLocation);
                link.save();
            } catch (Exception e) {
                System.out.println("An error occurred when trying to create a desktop shortcut...");
                e.printStackTrace();
            }
        }
    }
}